using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;
using System.Text;

namespace PureRobot.Digi.ConnectME.JumpStart.SerialFun
{

    /// <summary>
    /// Extended serial port class
    /// </summary>
    /// <remarks>This class is very buggy and should not be used commercially.</remarks>
    public class SerialPortEx : IDisposable
    {
        private SerialPort port;

        /// <summary>
        /// Initializes a new instance of the <see cref="SerialPortEx"/> class with UTF8 encoding.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        public SerialPortEx(SerialPort.Configuration configuration) : this(configuration, Encoding.UTF8)
        {
        }

        /// <summary>
        /// Initializes a new instance of the SerialPort class.
        /// </summary>
        /// <param name="configuration">Serial port configuartion</param>
        /// <param name="encoding">The encoding to use to translate read data.</param>
        public SerialPortEx(SerialPort.Configuration configuration, Encoding encoding)
        {
            this.port = new SerialPort(configuration);
            this.DataEncoding = new System.Text.UTF8Encoding();
            this.ReadTimeout = Timeout.Infinite;
            this.LineCharacters = "\n";
            this.Echo = false;
        }

        /// <summary>
        /// Initializes a new instance of the SerialPort class.
        /// </summary>
        /// <param name="comPort">The port to use (for example, COM1). </param>
        /// <param name="speed">The baud rate</param>
        /// <param name="flowControl">Enable flow control</param>
        public SerialPortEx(SerialPort.Serial comPort, SerialPort.BaudRate speed, bool flowControl)
            : this(new SerialPort.Configuration(comPort, speed, flowControl))
        {
        }

        /// <summary>
        /// Writes a specified number of bytes to an output buffer at the specified offset.
        /// </summary>
        /// <param name="buffer">The byte array to write the output to.</param>
        /// <param name="offset">The offset in the buffer array to begin writing.</param>
        /// <param name="count">The number of bytes to write.</param>
        public void Write(byte[] buffer, int offset, int count)
        {
            this.port.Write(buffer, offset, count);
        }

        /// <summary>
        /// Writes the parameter string to the output. 
        /// </summary>
        /// <param name="text">The string to write to the output buffer.</param>
        public void Write(string text)
        {
            if (text == null)
                throw new ArgumentNullException();

            if (text.Length > 0)
            {
                byte[] data = this.DataEncoding.GetBytes(text);
                Write(data, 0, data.Length);
            }
        }

        /// <summary>
        /// Writes the specified string and the NewLine value to the output buffer. 
        /// </summary>
        /// <param name="text">The string to write to the output buffer.</param>
        public void WriteLine(string text)
        {
            Write(text + this.WriteLineCharacters);
        }

        /// <summary>
        /// Writes an empty line.
        /// </summary>
        public void WriteLine()
        {
            WriteLine(string.Empty);
        }

        /// <summary>
        /// Reads a number of bytes from the SerialPort input buffer and writes those bytes into a byte array at the specified offset.
        /// </summary>
        /// <param name="buffer">The byte array to write the input to. </param>
        /// <param name="offset">The offset in the buffer array to begin writing.</param>
        /// <param name="count">The number of bytes to read. </param>
        /// <param name="timeout">The timeout for read operation</param>
        /// <returns>The number of bytes to read. </returns>
        public int Read(byte[] buffer, int offset, int count, int timeout)
        {
            return this.port.Read(buffer, offset, count, timeout);
        }

        /// <summary>
        /// Reads a number of bytes from the SerialPort input buffer and writes those bytes into a byte array at the specified offset.
        /// </summary>
        /// <param name="buffer">The byte array to write the output to. </param>
        /// <param name="offset">The offset in the buffer array to begin writing. </param>
        /// <param name="count">The number of bytes to write.</param>
        /// <returns>The number of bytes read.</returns>
        public int Read(byte[] buffer, int offset, int count)
        {
            return this.port.Read(buffer, offset, count, this.ReadTimeout);
        }

        /// <summary>
        /// Reads up to the NewLine value in the input buffer
        /// </summary>
        /// <returns>The contents of the input buffer up to the first occurrence of a NewLine value.</returns>
        public string ReadLine()
        {
            return ReadTo(this.ReadLineCharacters);
        }

        /// <summary>
        /// Reads a string up to the specified value in the input buffer.
        /// </summary>
        /// <param name="value">A value that indicates where the read operation stops.</param>
        /// <returns>The contents of the input buffer up to the specified value.</returns>
        public string ReadTo(string value)
        {
            string textArrived = string.Empty;

            // Arguments check
            if (value == null)
                throw new ArgumentNullException();

            if (value.Length == 0)
                throw new ArgumentException();

            //Read from the serial port the length of the search value bytes at a time, in order to make sure the encoding will capture enough data to convert into a string (e.g. if using Unicode, this will be two bytes)
            byte[] buffer = new byte[DataEncoding.GetBytes(value).Length];

            // Read until the value is read or no data is received in the timeout period
            bool match = false;
            while (!match)
            {
                Array.Clear(buffer, 0, buffer.Length);
                int bytesRead = port.Read(buffer, 0, buffer.Length, this.ReadTimeout);

                // if nothing was read (timeout), we will return null
                if (bytesRead <= 0)
                {
                    return null;
                }

                //If set to echo during reads, write the received data back to the port
                if (Echo)
                {
                    port.Write(buffer, 0, bytesRead);
                }
                
                // Decode received bytes into chars and then into string
                char[] charData = DataEncoding.GetChars(buffer);
                textArrived += new string(charData);

                //Check for a match in the received text
                match = textArrived.IndexOf(value) != -1;
            }            

            // chop end pattern
            if (textArrived.Length >= value.Length)
                textArrived = textArrived.Substring(0, textArrived.Length - value.Length);

            return textArrived;
        }

        /// <summary>
        /// Clears all buffers and causes any buffered data to be written to the SerialPort
        /// </summary>
        public void Flush()
        {
            this.port.Flush();
        }

        #region IDisposable Members

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        public void Dispose()
        {
            port.Dispose();
            port = null;
        }

        #endregion

        /// <summary>
        /// Gets or sets the byte encoding for pre- and post-transmission conversion of text.
        /// </summary>
        public System.Text.Encoding DataEncoding;

        /// <summary>
        /// Gets or sets the line chars used to interpret the end of a call to the ReadLine method or appended to the output in a WriteLine call.
        /// </summary>
        /// <value>The line chars.</value>
        public string LineCharacters
        {
            get
            {
                return ReadLineCharacters;
            }
            set
            {
                ReadLineCharacters = value;
                WriteLineCharacters = value;
            }
        }

        /// <summary>
        /// Gets or sets the value used to interpret the end of a call to the ReadLine method.
        /// </summary>
        public string ReadLineCharacters;

        /// <summary>
        /// Gets or sets the value used to append in the WriteLine method.
        /// </summary>
        public string WriteLineCharacters;

        /// <summary>
        /// Gets or sets the number of milliseconds before a time-out occurs when a read operation does not finish.
        /// </summary>
        public int ReadTimeout;

        /// <summary>
        /// During read operations, echo received input
        /// </summary>
        public bool Echo;
    }
}
